﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using psafth.Common.Extensions;

namespace psafth.AMB
{
    public class AMBCalculator
    {
        /// <summary>
        /// Gets the passingtime as DateTime.
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        internal static DateTime GetPassingTime(byte[] d)
        {
            try
            {
                // Get the microseconds since 1970-01-01:00:00
                ulong epoch = d.ToULong();

                // return a datetime from seconds
                return FromSeconds(epoch);
            }
            catch (Exception)
            {
                // TODO: Implement logging.

                // Return this very moment.
                return DateTime.Now;
            }
        }

        /// <summary>
        /// Returns the value of a datastring. In this case the TimeStamp from the passing data.
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        internal static ulong GetTimeStamp(byte[] d)
        {
            return d.Skip(d.Length - 8).Take(4).ToArray().ToULong();
            //ulong t = HexToLong(ArrJoiner(s, 4, s.Length - 8), true);
        }

        /// <summary>
        /// Calculates the TimeStamp to seconds.
        /// </summary>
        /// <param name="millionths"></param>
        /// <returns></returns>
        internal static double LapTime(long millionths)
        {
            double seconds = (millionths / 1000000.0);

            return seconds;
        }

        /// <summary>
        /// Returns a double with a given set of decimals
        /// </summary>
        /// <param name="d">The double</param>
        /// <param name="n">Number of decimals</param>
        /// <returns></returns>
        public static double TrimDouble(double d, int n)
        {
            return Math.Round(d, n);
        }

        /// <summary>
        /// Removes the CRC bytes and calculates the CRC
        /// </summary>
        /// <param name="bytes">byte array</param>
        /// <returns>Calculated crc</returns>
        public static uint CalculateCRC16(byte[] bytes)
        {
            // Array to hold the new bytes.
            byte[] tmpBytes = new byte[bytes.Length];

            byte currentByte = 0;

            // Loop through the bytes to insert the bytes to the new array. Set the CRC bytes to 0x00
            for (int i = 0; i < bytes.Length; i++)
            {
                currentByte = (i == 4 || i == 5 ? Convert.ToByte(0x00) : bytes[i]);
                tmpBytes[i] = currentByte;
            }

            // Return the CRC16.
            return CRC16.Calculate(tmpBytes);
        }

        /// <summary>
        /// Reverses the pair of a array.
        /// </summary>
        /// <param name="byteArray">the array</param>
        /// <returns>the array with reverse pairs.</returns>
        public static byte[] ReversePairs(byte[] byteArray)
        {
            // Loop through the array.
            for (int i = 0; i < byteArray.Length; i++)
            {
                // Check if the index is odd or even. If even, swap with the previous index
                if (i != 0 && i % 2 == 0)
                {
                    byte currentByte = byteArray[i];
                    byte previousByte = byteArray[i - 1];

                    byteArray[i] = previousByte;
                    byteArray[i - 1] = currentByte;
                }
            }
            return byteArray;
        }

        /// <summary>
        /// Calculates a given timestamp(epoch) to a DateTime.
        /// </summary>
        /// <param name="unixTime">timestamp (epoch)</param>
        /// <returns>DateTime.</returns>
        internal static DateTime FromSeconds(ulong unixTime)
        {
            try
            {
                var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                return epoch.AddMilliseconds(unixTime / 1000);
            }
            catch
            {
                return DateTime.Now;
            }
        }

        /// <summary>
        /// Un escapes a given array of bytes
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static byte[] UnEscape(byte[] bytes)
        {
            // List to store the unescaped bytes in.
            List<byte> newBytes = new List<byte>();

            for (int i = 0; i < bytes.Length; i++)
            {
                // Check that the byte is not End Of Record
                if (bytes[i] != AMBMessage.EOR)
                {
                    // Check for ESC byte.
                    if (bytes[i] == AMBMessage.ESC && ((i + 1) < bytes.Length))
                    {
                        // ESC byte found. Get the following byte and subtract with ESCxB
                        byte b = bytes[i + 1];
                        b -= AMBMessage.ESCxB;

                        // Add the byte to the list (and ignore the ESC byte).
                        newBytes.Add(b);
                    }
                    else
                    {
                        // Byte was not ESC. Add it to the list.
                        newBytes.Add(bytes[i]);
                    }
                }
                else
                {
                    // Byte was EOR. Add it to the list.
                    newBytes.Add(bytes[i]);
                }
            }

            return bytes;
        }

        /// <summary>
        /// Escapes a AMB command byte array
        /// </summary>
        /// <param name="bytes">AMB command byte array</param>
        /// <returns>Escaped AMB command byte array</returns>
        public static byte[] Escape(byte[] bytes)
        {
            // List to hold the escaped bytes
            List<byte> newBytes = new List<byte>();

            // Loop through the given array to find bytes that needs to be escaped
            for (int i = 0; i < bytes.Length; i++)
            {
                // bytes that needs to be escaped is ESC or SOR/EOR that is not first/last in the sequence
                if ((bytes[i] == AMBMessage.ESC || bytes[i] == AMBMessage.SOR || bytes[i] == AMBMessage.EOR) && i != 0 && i != bytes.Length - 1)
                {
                    // Escape needed. Add escape char and calculate new byte value
                    newBytes.Add(Convert.ToByte(AMBMessage.ESC));
                    byte t = (byte)(Convert.ToByte(bytes[i]) + AMBMessage.ESCxB);

                    newBytes.Add(t);
                }
                else
                {
                    // No escape needed
                    newBytes.Add(bytes[i]);
                }
            }

            return newBytes.ToArray();
        }

        /// <summary>
        /// Calculates and inserts the CRC into a AMB command byte array
        /// </summary>
        /// <param name="bytes">AMB command byte array</param>
        /// <returns>AMB command byte array with calculated CRC</returns>
        public static byte[] InsertCRC(byte[] bytes)
        {
            // Calculate CRC16
            uint checkSum = AMBCalculator.CalculateCRC16(bytes);

            // Get the CRC as bytes
            byte[] crcBytes = BitConverter.GetBytes(checkSum);

            // Insert CRC bytes to array
            bytes[4] = crcBytes[0];
            bytes[5] = crcBytes[1];

            return bytes;
        }
    }
}
